using Godot;
using System;
using System.Collections.Generic;

namespace DRE.Battle {
	public class Battle : Control {
		// DO NOT CHANGE.
		// Currently no plan for extended character count.
		public const int MaxPlayerCharacters = 3;
		public const int MaxEnemyCharacters = 3;

		public static Battle Instance { get; private set; }

		public Tp Tp { get; private set; }

		#region Characters
		#region Player
		private PlayerCharacter[] playerCharacters = new PlayerCharacter[MaxPlayerCharacters];
		List<PlayerCharacter> availablePlayerCharacterList = new List<PlayerCharacter>(MaxPlayerCharacters);
		bool availablePlayerCharacterCached = false;
		public IList<PlayerCharacter> AvailablePlayerCharacters {
			get {
				if (availablePlayerCharacterCached) {
					return availablePlayerCharacterList;
				}

				availablePlayerCharacterList.Clear();
				for (int i = 0; i < MaxPlayerCharacters; i += 1) {
					if (playerCharacters[i] != null) {
						availablePlayerCharacterList.Add(playerCharacters[i]);
					}
				}

				return availablePlayerCharacterList;
			}
		}
		public Character GetPlayerCharacter(int index) => playerCharacters[index];
		static readonly int[] playerPanelSeparation = { 0, 0, 7, -1 };
		public void SetPlayerCharacter(int index, PlayerCharacter obj) {
			PlayerCharacter original = playerCharacters[index];
			if (original != null) {
				original.Index = -1;
			}
			playerCharacters[index] = obj;
			if (obj != null) {
				obj.Index = index;
			}

			// TODO: Update player panels.
			playerPanelRoot.Set("custom_constants/separation", playerPanelSeparation[AvailablePlayerCharacters.Count]);
			playerPanels[index].Visible = (obj != null);
		}
		#endregion

		#region Enemy
		private EnemyCharacter[] enemyCharacters = new EnemyCharacter[MaxEnemyCharacters];
		List<EnemyCharacter> availableEnemyCharacterList = new List<EnemyCharacter>(MaxEnemyCharacters);
		bool availableEnemyCharacterCached = false;
		public IList<EnemyCharacter> AvailableEnemyCharacters {
			get {
				if (availableEnemyCharacterCached) {
					return availableEnemyCharacterList;
				}

				availableEnemyCharacterList.Clear();
				for (int i = 0; i < MaxEnemyCharacters; i += 1) {
					if (enemyCharacters[i] != null) {
						availableEnemyCharacterList.Add(enemyCharacters[i]);
					}
				}

				return availableEnemyCharacterList;
			}
		}
		public EnemyCharacter GetEnemyCharacter(int index) => enemyCharacters[index];
		public void SetEnemyCharacter(int index, EnemyCharacter obj) {
			EnemyCharacter original = enemyCharacters[index];
			if (original != null) {
				original.Index = -1;
			}
			enemyCharacters[index] = obj;
			if (obj != null) {
				obj.Index = index;
			}
		}
		#endregion
		#endregion

		#region State
		public enum State {
			Null = -1,
			Intro,
			Menu,
			Action,
			Fight,
			Turn,
			Result,
			Outro
		}

		static Dictionary<State, Action<Battle>> stateChangeActions = new Dictionary<State, Action<Battle>>(){
			{State.Null,(battle)=>{}},
			{State.Intro,(battle)=>{}},
			{State.Menu,(battle)=>{}},
			{State.Action,(battle)=>{}},
			{State.Fight,(battle)=>{}},
			{State.Turn,(battle)=>{}},
			{State.Result,(battle)=>{}},
			{State.Outro,(battle)=>{}},
		};

		private State currentState = State.Null;
		public State CurrentState {
			get => currentState;
			set {
				currentState = value;
				stateChangeActions[currentState](this);
			}
		}
		public State NextState { get; set; } = State.Null;
		public void GotoNextState() {
			CurrentState = NextState;
		}
		#endregion

		#region Menu
		public enum Menu {
			Null = -1,
			Button,
			Character,
			Act,
			Item
		}
		private Dictionary<Menu, DRE.Battle.Menu> menus = new Dictionary<Menu, DRE.Battle.Menu>();

		/// <summary>
		/// Get specified menu controller.
		/// </summary>
		/// <param name="menu">The menu to get.</param>
		/// <returns>
		/// Specified menu controller.
		/// Return null when not found.
		/// </returns>
		public DRE.Battle.Menu GetMenu(Menu menu) => (menus.ContainsKey(menu) ? menus[menu] : null);

		private Menu currentMenu = Menu.Null;
		public Menu CurrentMenu {
			get => currentMenu;
			set {
				currentMenu = value;
				GetMenu(currentMenu)?.OnMenuStart();
			}
		}
		public Menu NextMenu { get; set; } = Menu.Null;
		public void GotoNextMenu() {
			if (CurrentState != State.Menu) {
				return;
			}
			CurrentMenu = NextMenu;
		}
		public int MenuPlayer { get; set; } = 0;
		public void GotoNextMenuPlayer() {
			if (CurrentState != State.Menu) {
				return;
			}
			//TODO
		}
		#endregion

		#region Action

		#endregion

		#region Fight

		#endregion

		#region Turn
		public enum TurnState {
			Null = -1,
			Preparation,
			InTurn,
			Resetting
		}
		static Dictionary<TurnState, Action<Battle>> turnStateChangeActions = new Dictionary<TurnState, Action<Battle>>(){
			{TurnState.Null,(battle)=>{}},
			{TurnState.Preparation,(battle)=>{}},
			{TurnState.InTurn,(battle)=>{}},
			{TurnState.Resetting,(battle)=>{}}
		};

		private TurnState currentTurnState = TurnState.Null;
		public TurnState CurrentTurnState {
			get => currentTurnState;
			set {
				currentTurnState = value;
				turnStateChangeActions[currentTurnState](this);
			}
		}
		public bool TurnPreparationAutoEnd { get; set; } = true;
		public float TurnTime { get; set; } = 0;
		#endregion

		PlayerPanel[] playerPanels = new PlayerPanel[MaxPlayerCharacters];
		public PlayerPanel GetPlayerPanel(int index) => playerPanels[index];

		HBoxContainer playerPanelRoot;
		PackedScene playerPanelScene;

		public override void _Ready() {
			Tp = GetNode<Tp>("UI/Tp");
			playerPanelRoot = GetNode<HBoxContainer>("UI/Menu/Players/PlayerPanels");
			playerPanelScene = GD.Load<PackedScene>("res://Generic/Battle/PlayerPanel.tscn");
			for (int i = 0; i < MaxPlayerCharacters; i += 1) {
				PlayerPanel node = playerPanelScene.Instance() as PlayerPanel;
				playerPanelRoot.AddChild(node);
				playerPanels[i] = node;
				node.Hide();
			}

			NextState = State.Intro;
		}
		public override void _Process(float delta) {

		}
		public override void _EnterTree() {
			Instance=this;
		}
		public override void _ExitTree() {
			Instance=null;
		}
	}
}
